package com.ismarkendenis.feistelcipher;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class FeistelCipher {
	// Number of rounds for each block.
	private static final int ROUNDS = 10;
	// Constant value of block size.
	private static final int BLOCKSIZE = 8;
	// Separator for every block to determine the left and the right side.
	private static final int BLOCKSEP = 4;
	
	/**
	 * Function that encodes a text with the given key using the Feistel Cipher.
	 * @param text
	 * @param key
	 * @return encodedtext
	 */
	public String encode(String text, byte[] key) 
	{
		String[] keyset  = getKeys(key, ROUNDS);
		text             = addPadding(text);
		StringBuilder sb = new StringBuilder();
		String block;
		
		for (int i = 0; i < (text.length() / BLOCKSIZE); ++i) 
		{
			block = text.substring(BLOCKSIZE * i, BLOCKSIZE * (i + 1));

			for (int j = 0; j < ROUNDS; ++j) 
			{
				block = encodeBlock(block, keyset[j]);
			}
			sb.append(block);
		}

		return sb.toString();
	}

	/**
	 * Function that decodes an encoded text with the given key using the Feistel Cipher.
	 * @param enctext
	 * @param key
	 * @return decodedtext
	 */
	public String decode(String enctext, byte[] key) 
	{
		String[] keyset = getKeys(key, ROUNDS);
		StringBuilder sb = new StringBuilder();
		String block;
		
		for(int i = 0; i < (enctext.length() / BLOCKSIZE); ++i)
		{
			block = enctext.substring(BLOCKSIZE * i, BLOCKSIZE * (i + 1));
			// Switch the left and the right side
			block = block.substring(BLOCKSEP) + block.substring(0, BLOCKSEP);

			for(int j = ROUNDS; j > 0; --j)
			{
				block = encodeBlock(block, keyset[j-1]);
			}
			// Switch the left and the right side again.
			block = block.substring(BLOCKSEP) + block.substring(0, BLOCKSEP);
			sb.append(block);
		}

		return sb.toString();
	}
	
	/**
	 * Padding function that adds empty space at the end of the text to meet the block size of 8 bytes.
	 * @param plaintext
	 * @return paddedtext
	 */
	private String addPadding(String text)
	{
		while (text.length() % BLOCKSIZE != 0) 
		{
			text += "\0";
		}
		
		return text;
	}

	/**
	 * Returns the keys for every round.
	 * @param key
	 * @param r
	 * @return collection of keys
	 */
	private String[] getKeys(byte[] key, int r) 
	{
		String[] keyset = new String[r];
		
		try 
		{
			MessageDigest sha1Key = MessageDigest.getInstance("SHA1");
			sha1Key.update(key);
			
			for (int i = 0; i < r; ++i) 
			{
				byte[] hash = sha1Key.digest();
				keyset[i] = (new String(hash)).substring(0, BLOCKSIZE);
				sha1Key.update(keyset[i].getBytes());
			}
		} 
		catch (NoSuchAlgorithmException x) 
		{
			return null;
		}
		System.out.println(keyset[0]);
		return keyset;
	}

	/**
	 * Encodes the current block. 
	 * @param textblock
	 * @param key
	 * @return encodedBlock
	 */
	private String encodeBlock(String tb, String key) 
	{
		String left   = tb.substring(0, BLOCKSEP);
		String right  = tb.substring(BLOCKSEP, BLOCKSIZE);
		String result = right;
		
		for (int i = 0; i < BLOCKSEP; ++i) 
		{
			result += (char) (left.charAt(i) ^ ((char) (right.charAt(i) ^ key.charAt(i))));
		}

		return result;
	}
	
	public static String bitsToString(byte[] bytes)
	{
		String word = "";
		
		for (byte b : bytes)
		{
			word += b;
		}
		
		return word;
	}	
}
